package com.hyacinth.dubbo.io.netty.util;

import com.hyacinth.dubbo.io.netty.serialize.Serializer;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import lombok.experimental.PackagePrivate;
import lombok.extern.slf4j.Slf4j;

import java.io.File;
import java.lang.reflect.Method;
import java.net.URL;
import java.util.HashSet;
import java.util.Set;

/**
 * @author zhangfb
 * @version 1.0.0.1
 * @since JDK 1.8
 */
@Slf4j
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class ClassUtil {

    private static final String CLASS_SUFFIX = ".class";

    public static void main(String[] args) {
        Class<Serializer> superClz = Serializer.class;
        Set<Class<? extends Serializer>> classes = loadSubClass(superClz);
        System.out.println(classes);
    }

    /**
     * 查询所有子类
     * @param superClz 父类
     */
    public static <T> Set<Class<? extends T>> loadSubClass(Class<? extends T> superClz) {
        String packName = superClz.getPackage().getName();
        return loadSubClass(packName, superClz);
    }

    /**
     * 查询所有子类
     * @param superClz 父类
     */
    public static <T> Set<Class<? extends T>> loadSubClass(String packName, Class<? extends T> superClz) {
        // 获取父类资源
        URL url = superClz.getResource("/");
        // 获取资源文件
        String fileName = url.getFile();
        String pathName = fileName.replaceFirst("/", "");
        File rootFile = new File(pathName);
        Set<Class<? extends T>> classSet = new HashSet<>();
        setSubClassList(rootFile, rootFile.getPath() + "\\", packName, superClz, classSet);
        return classSet;
    }

    /**
     * 遍历bin目录下所有文件
     * 若是文件夹，则递归将文件夹内文件添加到文件数组
     * 若是文件，则将文件加载并强制类型转换为要查找子类的父类，转换无异常，说明加载类是父类的子类
     * (当遍历到类自身时，此处认为也属于子类，当然这是我的一厢情愿，我肯定不告诉你--〉我嫌麻烦。。
     *  不过我还是加上了判断--〉
     * )
     */
     public static <T> void setSubClassList(File classFile, String rootDir, String packName, Class<? extends T> superClz, Set<Class<? extends T>> subClassSet) {
        if (classFile.isDirectory()) {
            File[] files = classFile.listFiles();
            if (files == null || files.length < 1) {
                return;
            }
            for (File file : files) {
                setSubClassList(file, rootDir, packName, superClz, subClassSet);
            }
        } else {
            String path = classFile.getPath();
            String packResName = resourceName(packName);
            if (path.contains(CLASS_SUFFIX) && path.contains(packResName)) {
                try {
                    String targetClassName = path.replace(rootDir, "").replace(CLASS_SUFFIX, "").replace("\\", ".");
                    Class<?> targetClass = Class.forName(targetClassName);
                    // 目标类存在 && 目标类与父类可转让 && 目标类非父类
                    if (targetClass != null && superClz.isAssignableFrom(targetClass) && !superClz.equals(targetClass)) {
                        subClassSet.add((Class<? extends T>) targetClass);
                    }
                } catch (ClassNotFoundException e) {
                    log.error("获取父类的所有子类出错，父类名={}",superClz.getName(), e);
                }
            }
        }
    }

    /**
     * 包名转换为资源名
     * @param packName 包名 com.hyacinth.dubbo.io
     * @return 资源名
     */
    private static String resourceName(String packName) {
        if (packName != null) {
            String resourceName = packName.replace(".", "\\");
            if (resourceName.startsWith("/")) {
                resourceName = resourceName.substring(1);
            }
            return resourceName;
        }
        return null;
    }

    /**
     * 获取默认类加载器
     * @return ClassLoader
     */
    public static ClassLoader getDefaultClassLoader() {
        ClassLoader cl = null;
        try {
            cl = Thread.currentThread().getContextClassLoader();
        } catch (Throwable ex) {
            log.debug("Cannot access thread context ClassLoader - falling back...");
        }
        if (cl == null) {
            cl = ClassUtil.class.getClassLoader();
            if (cl == null) {
                try {
                    cl = ClassLoader.getSystemClassLoader();
                } catch (Throwable ex) {
                    log.debug("Cannot access system ClassLoader - oh well, maybe the caller can live with null...");
                }
            }
        }
        return cl;
    }

    /**
     * 获取某个类的方法
     * @param clazz the clazz to analyze
     * @param methodName the name of the method
     * @param paramTypes the parameter types of the method
     * @return the method, or {@code null} if not found
     * @see Class#getMethod
     */
    public static Method getMethodIfAvailable(Class<?> clazz, String methodName, Class<?>... paramTypes) {
        AssertUtil.notNull(clazz, "类不能为空");
        AssertUtil.notNull(methodName, "方法签名不能为空");
        Method method = null;
        if (paramTypes != null) {
            try {
                method = clazz.getMethod(methodName, paramTypes);
            } catch (NoSuchMethodException ex) {
                log.error("Expected method not found", ex);
            }
        } else {
            Set<Method> candidates = new HashSet<>(1);
            Method[] methods = clazz.getMethods();
            for (Method m : methods) {
                if (methodName.equals(m.getName())) {
                    candidates.add(m);
                }
            }
            if (candidates.size() == 1) {
                method =  candidates.iterator().next();
            } else if (candidates.isEmpty()) {
                log.error("Expected method not found: " + clazz.getName() + '.' + methodName);
            } else {
                log.error("No unique method found: " + clazz.getName() + '.' + methodName);
            }
        }
        return method;
    }

}
